package main

import (
	"errors"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	_ "github.com/go-sql-driver/mysql"
	"github.com/go-xorm/xorm"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/middleware/recover"
	"log"
	"strings"
	"time"
)

var x *xorm.Engine

type ProductType struct{
	Id int `xorm:"pk"`
	Name string
	CreatedAt time.Time
  	UpdatedAt time.Time
}

type Product struct{
	Id int `xorm:"pk"`
	Name string 
	Type ProductType `xorm:"-"`
	TypeId int
	CreatedAt time.Time
  	UpdatedAt time.Time
}

type ProjectType struct{
	Id int `xorm:"pk"`
	Name string
	CreatedAt time.Time
  	UpdatedAt time.Time 
}

type Project struct{
	Id int `xorm:"pk"`
	Name string
	Type ProjectType `xorm:"-"`
	TypeId int
	CreatedAt time.Time
  	UpdatedAt time.Time
}

type Meal struct{
	Id int `xorm:"pk"`
	Name string 
	ProjectList []*Project `xorm:"-"`
	ProductList []*Product `xorm:"-"`
	CreatedAt time.Time
  	UpdatedAt time.Time
}

type Member struct{
	Id int `xorm:"pk"`
	Name string
	Age int 
	Meallist []*Meal `xorm:"-"`
	CreatedAt time.Time
  	UpdatedAt time.Time
}
 
type MealProduct struct{
	Id int `xorm:"pk"`
	MealId int
	ProductId int
	CreatedAt time.Time
  	UpdatedAt time.Time
}

type MealProject struct{
	Id int `xorm:"pk"`
	MealId int
	ProjectId int
	CreatedAt time.Time
  	UpdatedAt time.Time
}

type MemberMeal struct{
	Id int `xorm:"pk"`
	MemberId int
	MealId int
	CreatedAt time.Time
  	UpdatedAt time.Time
}

// MyClaims 自定义声明结构体并内嵌jwt.StandardClaims
// jwt包自带的jwt.StandardClaims只包含了官方字段
// 我们这里需要额外记录一个username字段，所以要自定义结构体
// 如果想要保存更多信息，都可以添加到这个结构体中
type MemberClaims struct {
	Username string `json:"username"`
	jwt.StandardClaims
}

const TokenExpireDuration = time.Hour * 2
var MemberSecret = []byte("你值得拥有")

func init() {
	var xErr error
	x, xErr = xorm.NewEngine("mysql", "root:new_password@tcp(127.0.0.1:3306)/insert_xorm?charset=utf8")
	if xErr != nil {
		log.Fatal("数据库连接失败:", xErr)
	}
	if err := x.Sync(new(ProductType), new(Product), new(ProjectType), new(Project), new(Meal), new(Member), new(MealProduct), new(MealProject), new(MemberMeal)); err != nil {
		log.Fatal("数据表同步失败:", err)
	}
}

// GenToken 生成JWT
func GenToken(username string) (string, error) {
	// 创建一个我们自己的声明
	c := MemberClaims{
		"username", // 自定义字段
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
			Issuer: "member-project",                               // 签发人
		},
	}
	// 使用指定的签名方法创建签名对象
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
	// 使用指定的secret签名并获得完整的编码后的字符串token
	return token.SignedString(MemberSecret)
}

// ParseToken 解析JWT
func ParseToken(tokenString string) (*MemberClaims, error) {
	// 解析token
	// token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) {
	// 	return MemberSecret, nil
	// })

	// 解析token
	token, err := jwt.ParseWithClaims(tokenString, &MemberClaims{}, func(token *jwt.Token) (i interface{}, err error) {
		return MemberSecret, nil
	})
	if err != nil {
		return nil, err
	}

	// if token.Valid {
	// 	fmt.Println(token.Claims) // 校验token
	// 	return token.Claims.(*MemberClaims), nil   //报错
	// }

	if claims, ok := token.Claims.(*MemberClaims); ok && token.Valid { // 校验token
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

func TokenVerification(ctx iris.Context){
	
	// 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI
	// 这里假设Token放在Header的Authorization中，并使用Bearer开头
	// 这里的具体实现方式要依据你的实际业务情况决定
	authHeader := ctx.GetHeader("token")
	if authHeader == "" {
		ctx.JSON(iris.Map{"message": "请求头中auth为空", "requestCode": 2003})
		return
	}

	// 按空格分割
	parts := strings.SplitN(authHeader, " ", 2)
	if !(len(parts) == 2 && parts[0] == "Bearer") {
		ctx.JSON(iris.Map{"message": "请求头中auth格式有误", "requestCode": 2004})
		return
	}

	// parts[1]是获取到的tokenString，我们使用之前定义好的解析JWT的函数来解析它
	_, err := ParseToken(parts[1])
	if err != nil {
		ctx.JSON(iris.Map{"message": "无效的Token", "requestCode": 2005})
		return
	}

	ctx.Next()
	return
}

func Query(ctx iris.Context) { 
    var m []*Member
	x.Find(&m)

	for _, v := range m{
		x.Join("INNER", "member_meal", "member_meal.meal_id = meal.id").Where("member_meal.member_id = ?", v.Id).Find(&v.Meallist)
		fmt.Println(v.Meallist)
		for _, mv := range v.Meallist{
			x.Join("INNER", "meal_project", "meal_project.project_id = project.id").Where("meal_project.meal_id = ?", mv.Id).Find(&mv.ProjectList)
			fmt.Println(mv.ProjectList)
			for _, jv := range mv.ProjectList{
				x.Join("INNER", "project", "project_type.id = project.type_id").Where("project.id = ?",jv.Id).Get(&jv.Type)
				fmt.Println(jv.Type)
			}
			
			x.Join("INNER", "meal_product", "meal_product.product_id = product.id").Where("meal_product.meal_id = ?", mv.Id).Find(&mv.ProductList)
			fmt.Println(mv.ProductList)
			for _, dv := range mv.ProductList{
				x.Join("INNER", "product", "product_type.id = product.type_id").Where("product.id = ?", dv.Id).Get(&dv.Type)
				fmt.Println(dv.Type)
			}	
		}
	}

	ctx.JSON(iris.Map{"message": "成功", "requestCode": 200, "data" : m})
}

func Cors(ctx iris.Context) {

	ctx.Header("Access-Control-Allow-Origin", "*")

	//ctx.Header("Access-Control-Allow-Headers", "DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization, token")
	//
	//ctx.Header("Access-Control-Allow-Methods", "GET,POST,OPTIONS,HEAD,DELETE")

	if ctx.Method() == "OPTIONS" {

		ctx.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,PATCH,OPTIONS")

		ctx.Header("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, Token, token")

		ctx.StatusCode(204)

		return

	}

	ctx.Next()

}

func main(){ 

	app := iris.New()

	app.Use(recover.New())
	app.Use(Cors)
	common := app.Party("/")
	{
		common.Options("*", func(ctx iris.Context) {
			ctx.Next()
		})
	}

	app.Post("/login", func(ctx iris.Context){

		// 用户发送用户名和密码过来
		user := ctx.PostValue("user")
		psw := ctx.PostValue("psw")
		fmt.Println(user, psw)

		// 校验用户名和密码是否正确
		if user == "yangliang" && psw == "yangliang123" {
			// 生成Token
			tokenString, _ := GenToken(user)
			ctx.JSON(iris.Map{"message": "success", "code" : 2000, "token": tokenString})
			return 
		}

		ctx.JSON(iris.Map{"message": "鉴权失败", "code" : 2002})
		return
	})
	
	app.Post("/query", TokenVerification, Query)
	app.Run(iris.Addr(":0006"))

}